﻿using System;
using Game.Lib.ztPackets;
using System.Collections.Generic;

namespace Game.Lib
{
    public abstract class Packet
    {
        /// <summary>
        /// The type of the packet
        /// </summary>
        public PacketType Type;
        
        /// <summary>
        /// The semi-unique ID of the packet
        /// </summary>
        public uint ID;
        
        /// <summary>
        /// The authentication hash of the packet
        /// </summary>
        public int Hash;

        public abstract int Length { get; }

        public abstract byte[] ToBytes();
        public abstract void FromBytes(byte[] data, int pos);

        public MicroPacket[] ToMicroPackets()
        {
            byte[] data = ToBytes();
            int count = data.Length / (MicroPacket.MAXIMUM_SIZE + 1) + 1;
            MicroPacket[] ret = new MicroPacket[count];
            byte[] buf = new byte[MicroPacket.MAXIMUM_SIZE];
            for(int i = 0; i < count - 1; i++)
            {
                Buffer.BlockCopy(data, i * MicroPacket.MAXIMUM_SIZE, buf, 0, MicroPacket.MAXIMUM_SIZE);
                ret[i] = new MicroPacket(buf, ID, (ushort)i, (ushort)count);
            }
            int remainder = data.Length % MicroPacket.MAXIMUM_SIZE;
            buf = new byte[remainder];
            ret[count - 1] = new MicroPacket(buf, ID, (ushort)(count - 1), (ushort)count);
            return ret;
        }

        public static void FromMicroPackets(List<MicroPacket> data)
        {
            data.Sort();
            int count = data[0].Count;
            int length = count * MicroPacket.MAXIMUM_SIZE + data[count - 1].Length;
            byte[] ret = new byte[length];
            int pos = 0;
            for(int i = 0; i < count; i++)
            {
                Buffer.BlockCopy(data[i].Data, 0, ret, pos, data[i].Length);
                pos += data[i].Length;
            }

            Reconstruct(ret, 0);
        }

        public static Packet Reconstruct(byte[] data, int pos)
        {
            Packet p = BuildType(data, pos);
            p.Type = (PacketType)BitConverter.ToUInt16(data, pos);
            p.ID = BitConverter.ToUInt32(data, pos + 2);
            p.Hash = BitConverter.ToInt32(data, pos + 6);
            p.FromBytes(data, pos + 10);
            return p;
        }
        
        internal byte[] GetTotalBuffer(byte[] data)
        {
            byte[] ret = new byte[data.Length + 10];
            Buffer.BlockCopy(BitConverter.GetBytes((ushort)Type), 0, ret, 0, 2);
            Buffer.BlockCopy(BitConverter.GetBytes(ID), 0, ret, 2, 4);
            UpdateHashCode(data, 0, data.Length);
            Buffer.BlockCopy(BitConverter.GetBytes(Hash), 0, ret, 6, 4);
            Buffer.BlockCopy(data, 0, ret, 10, data.Length);
            return ret;
        }
        
        private void UpdateHashCode(byte[] data, int pos, int length)
        {
            byte[] temp = new byte[length];
            Buffer.BlockCopy(data, pos, temp, 0, length);
            Hash =  temp.GetHashCode();
        }

        private static Packet BuildType(byte[] data, int pos)
        {
            PacketType Type = (PacketType)BitConverter.ToUInt16(data, pos);
            switch(Type)
            {
                case PacketType.Login: return new LoginPacket();
                case PacketType.Ping: return new PingPacket();
                case PacketType.TestMessage: return new TestMessagePacket();
                case PacketType.Ack: return new AckPacket();
                default: throw new ArgumentException("Invalid or unrecognized packet type");
            }
        }
    }
}
